#include "tools.h"
#include "server.h"
#include <pthread.h>
#include "memtool.h"
#include "types.h"
#include "net.h"
#include "pm.h"
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <string.h>


//return the id
int sync_param(struct input_param *param, struct mc_ctx *mc_ctx){
  if(param->master){
    char s_id[10];
    mt_set(mc_ctx, "server-num", (char *)&param->server_num, sizeof(param->server_num));
    mt_set(mc_ctx, "client-num", (char *)&param->client_num, sizeof(param->client_num));
    mt_set(mc_ctx, "pool-name", param->pool_name, sizeof(param->pool_name));
    mt_set(mc_ctx, "pool-size", (char *)&param->pool_size, sizeof(param->pool_size));
    mt_set(mc_ctx, "sync-param-ready", "1", 1);
    p_info("master boardcast done\n");
    mt_set(mc_ctx, "next-id", "0", 1);
    itoa(param->server_num - 1, s_id, 10);
    //wait until all id was distributed
    mt_wait_key_val(mc_ctx, "next-id", s_id);
    mt_set(mc_ctx, "id-distri", "1", 1);
    p_info("id was distributed\n");
    return 0;
  }else{
    int *i_tmp;
    long *l_tmp;
    char *s_tmp;
    int id;
    mt_wait_key(mc_ctx, "sync-param-ready");
    while(mt_get(mc_ctx, "server-num", (char **)&i_tmp, NULL));
    param->server_num = *i_tmp;
    free(i_tmp);

    while(mt_get(mc_ctx, "client-num", (char **)&i_tmp, NULL));
    param->client_num = *i_tmp;
    free(i_tmp);

    while(mt_get(mc_ctx, "pool-size", (char **)&l_tmp, NULL));
    param->pool_size = *l_tmp;
    free(l_tmp);

    while(mt_get(mc_ctx, "pool-name", (char **)&s_tmp, NULL));
    strcpy(param->pool_name, s_tmp);
    free(s_tmp);

    id = mt_incr(mc_ctx, "next-id");
    p_info("client get master info: id %d server num %d client num %d pool name %s\n", 
            id, param->server_num, param->client_num, param->pool_name);
    mt_wait_key(mc_ctx, "id-distri");
    return id;
  }
}

int main(int argc, char *argv[]) {

  BUILD_BUG_ON(sizeof(int) != 4);
  BUILD_BUG_ON(sizeof(long) != 8);

  char c;
  char mc_ip[SHORT_STR_LEN] = {0};
  char pool_name[SHORT_STR_LEN] = {0};
  int client_num = 0, server_num = 0, master = 0, mc_port = 0;
  int ib_port = 1; //default use first port
  struct input_param *param;
  struct mc_ctx *mc_ctx;
  struct server_ctx *ctx;
  int id;
  long gsize;
  int res;

  static struct option opts[] = {
    {"master", 1, NULL, 'm'},
    {"server-num", 1, NULL, 's'},
    {"client-num", 1, NULL, 'c'},
    {"ib-port", 1, NULL, 'i'},
    {"memcached-server-ip", 1, NULL, 'X'},
    {"memcached-server-port", 1, NULL, 'P'},
    {"pool-name", 1, NULL, 'p'},
    {"G-size", 1, NULL, 'G'},
    {NULL, 0, NULL, 0}
  };

  srand(time(NULL));
  /* Parse and check arguments */
  while (1) {
    c = getopt_long(argc, argv, "m:s:c:X:P", opts, NULL);
    if (c == -1) {
      break;
    }
    switch (c) {
    case 'm':
      master = atoi(optarg);
      if(master)p_info("This is master node\n");
      break;
    case 's':
      server_num = atoi(optarg);
      p_info("server num: %d\n", server_num);
      break;
    case 'c':
      client_num = atoi(optarg);
      p_info("client num: %d\n", client_num);
      break;
    case 'i':
      ib_port = atoi(optarg);
      p_info("ib port :%d\n", ib_port);
      break;
    case 'X':
      if(strlen(optarg) >= SHORT_STR_LEN){
        p_die("ip string too long\n");
      }
      strcpy(mc_ip, optarg);
      p_info("memcached-server-ip = %s\n", mc_ip);
      break;
    case 'P':
      mc_port = atoi(optarg);
      p_info("memcached-server-port = %d\n",mc_port);
      break;
    case 'p':
      if(strlen(optarg) >= SHORT_STR_LEN){
        p_die("pool_name too long\n");
      }
      strcpy(pool_name, optarg);
      p_info("pool name %s\n", pool_name);
      break;
    case 'G':
      gsize = atoi(optarg);
      p_info("file size %ldG\n", gsize);
      break;
    default:
      p_die("Invalid argument %c\n", c);
    }
  }
  if(master){
    if(!(server_num && client_num && strlen(pool_name))){
      p_die("master node should have some information\n");
    }
  }
  param = (struct input_param *)malloc(sizeof(struct input_param));
  assert(param);
  param->ib_port = ib_port;
  param->master = master;
  param->mc_port = mc_port;
  strcpy(param->mc_ip, mc_ip);
  if(master){
    param->server_num = server_num;
    param->client_num = client_num;
    param->pool_size = gsize << 30;
    strcpy(param->pool_name, pool_name);
  }
  
  p_info("start sync param\n");
  mc_ctx = mt_create_ctx(mc_ip, mc_port);
  assert(mc_ctx);
  id = sync_param(param, mc_ctx);
  p_info("finish sync param\n");

  //create the env struct
  ctx = (struct server_ctx*)malloc(sizeof(struct server_ctx));
  assert(ctx);
  ctx->param = param;
  ctx->mi = mc_ctx;
  ctx->id = id;

  //create server's connection
  p_info("start server connection\n");
  server_conn_init(ctx);
  p_info("connect all server successfully\n");
  
  //create local mempool
  create_pm(param->pool_name, param->pool_size, &ctx->pm);
  //init pm data struct
  ctx->super = init_pm(&ctx->pm);
  //get allocator
  ctx->allocator = init_pm_allocator(&ctx->pm);
  assert(ctx->allocator);
  init_server_struct(ctx);
  //register memory and publish memory info as well as consistent hash
  sync_server_mr(ctx);
  sync_server_hash(ctx);
  //thread for user connection
  {
    pthread_t user_thread, server_poll_thread, client_poll_thread;
    res = pthread_create(&user_thread, NULL, user_routine, ctx);
    assert(!res);
    res = pthread_create(&server_poll_thread, NULL, server_poll_routine, ctx);
    assert(!res);
    res = pthread_create(&client_poll_thread, NULL, client_poll_routine, ctx);
    main_control(ctx);
    pthread_join(user_thread, NULL);
    pthread_join(server_poll_thread, NULL);
    pthread_join(client_poll_thread, NULL);
  }
  //thread for qp poll
  return 0;
}